View Javadoc

1   package se.datadosen.component;
2   
3   import java.awt.*;
4   import java.util.*;
5   
6   /***
7    * <p>RiverLayout makes it very simple to construct user interfaces as components
8    * are laid out similar to how text is added to a word processor (Components flow
9    * like a "river". RiverLayout is however much more powerful than FlowLayout:
10   * Components added with the add() method generally gets laid out horizontally,
11   * but one may add a string before the component being added to specify "constraints"
12   * like this:
13   * add("br hfill", new JTextField("Your name here");
14   * The code above forces a "line break" and extends the added component horizontally.
15   * Without the "hfill" constraint, the component would take on its preferred size.
16   *</p>
17   * <p>
18   * List of constraints:<ul>
19   * <li>br - Add a line break
20   * <li>p - Add a paragraph break
21   * <li>tab - Add a tab stop (handy for constructing forms with labels followed by fields)
22   * <li>hfill - Extend component horizontally
23   * <li>vfill - Extent component vertically (currently only one allowed)
24   * <li>left - Align following components to the left (default)
25   * <li>center - Align following components horizontally centered
26   * <li>right - Align following components to the right
27   * <li>vtop - Align following components vertically top aligned
28   * <li>vcenter - Align following components vertically centered (default)
29   * </ul>
30   * </p>
31   * RiverLayout is LGPL licenced - use it freely in free and commercial programs
32   *
33   * @author David Ekholm
34   * @version 1.0
35   */
36  public class RiverLayout
37      extends FlowLayout
38      implements LayoutManager, java.io.Serializable {
39  
40     public static final String LINE_BREAK = "br";
41     public static final String PARAGRAPH_BREAK = "p";
42     public static final String TAB_STOP = "tab";
43     public static final String HFILL = "hfill";
44     public static final String VFILL = "vfill";
45     public static final String LEFT = "left";
46     public static final String RIGHT = "right";
47     public static final String CENTER = "center";
48     public static final String VTOP = "vtop";
49     public static final String VCENTER = "vcenter";
50  
51     Map constraints = new HashMap();
52     String valign = VCENTER;
53     int hgap;
54     int vgap;
55     Insets extraInsets;
56     Insets totalInsets = new Insets(0, 0, 0, 0);// Dummy values. Set by getInsets()
57  
58  
59     public RiverLayout() {
60        this(10, 5);
61     }
62  
63     public RiverLayout(int hgap, int vgap) {
64        this.hgap = hgap;
65        this.vgap = vgap;
66        setExtraInsets(new Insets(0, hgap, hgap, hgap));
67     }
68  
69     /***
70      * Gets the horizontal gap between components.
71      */
72     public int getHgap() {
73         return hgap;
74     }
75  
76     /***
77      * Sets the horizontal gap between components.
78      */
79     public void setHgap(int hgap) {
80         this.hgap = hgap;
81     }
82  
83     /***
84      * Gets the vertical gap between components.
85      */
86     public int getVgap() {
87         return vgap;
88     }
89  
90     public Insets getExtraInsets() {
91        return extraInsets;
92     }
93  
94     public void setExtraInsets(Insets newExtraInsets) {
95       extraInsets = newExtraInsets;
96     }
97  
98     protected Insets getInsets(Container target) {
99        Insets insets = target.getInsets();
100       totalInsets.top = insets.top + extraInsets.top;
101       totalInsets.left = insets.left + extraInsets.left;
102       totalInsets.bottom = insets.bottom + extraInsets.bottom;
103       totalInsets.right = insets.right + extraInsets.right;
104       return totalInsets;
105    }
106 
107    /***
108     * Sets the vertical gap between components.
109     */
110    public void setVgap(int vgap) {
111        this.vgap = vgap;
112    }
113 
114 
115    /***
116     * @param name the name of the component
117     * @param comp the component to be added
118     */
119    public void addLayoutComponent(String name, Component comp) {
120       constraints.put(comp, name);
121    }
122 
123    /***
124     * Removes the specified component from the layout. Not used by
125     * this class.
126     * @param comp the component to remove
127     * @see       java.awt.Container#removeAll
128     */
129    public void removeLayoutComponent(Component comp) {
130       constraints.remove(comp);
131    }
132 
133    boolean isFirstInRow(Component comp) {
134       String cons = (String) constraints.get(comp);
135       return cons != null && (cons.indexOf(RiverLayout.LINE_BREAK) != -1 ||
136                               cons.indexOf(RiverLayout.PARAGRAPH_BREAK) != -1);
137    }
138 
139    boolean hasHfill(Component comp) {
140       return hasConstraint(comp, RiverLayout.HFILL);
141    }
142 
143    boolean hasVfill(Component comp) {
144       return hasConstraint(comp, RiverLayout.VFILL);
145    }
146 
147    boolean hasConstraint(Component comp, String test) {
148       String cons = (String) constraints.get(comp);
149       if (cons == null) return false;
150       StringTokenizer tokens = new StringTokenizer(cons);
151       while (tokens.hasMoreTokens())
152          if (tokens.nextToken().equals(test)) return true;
153       return false;
154    }
155 
156    /***
157     * Figure out tab stop x-positions
158     */
159    protected Ruler calcTabs(Container target) {
160       Ruler ruler = new Ruler();
161       int nmembers = target.getComponentCount();
162 
163       int x = 0;
164       int tabIndex = 0; // First tab stop
165       for (int i = 0; i < nmembers; i++) {
166          Component m = target.getComponent(i);
167 //         if (m.isVisible()) {
168             if (isFirstInRow(m) || i == 0) {
169                x = 0;
170                tabIndex = 0;
171             }
172             else x+= hgap;
173             if (hasConstraint(m, TAB_STOP)) {
174                ruler.setTab(tabIndex, x); // Will only increase
175                x = ruler.getTab(tabIndex++); // Jump forward if neccesary
176             }
177             Dimension d = m.getPreferredSize();
178             x += d.width;
179          }
180 //      }
181       return ruler;
182    }
183 
184    /***
185     * Returns the preferred dimensions for this layout given the
186     * <i>visible</i> components in the specified target container.
187     * @param target the component which needs to be laid out
188     * @return    the preferred dimensions to lay out the
189     *            subcomponents of the specified container
190     * @see Container
191     * @see #minimumLayoutSize
192     * @see       java.awt.Container#getPreferredSize
193     */
194    public Dimension preferredLayoutSize(Container target) {
195       synchronized (target.getTreeLock()) {
196          Dimension dim = new Dimension(0, 0);
197          Dimension rowDim = new Dimension(0, 0);
198          int nmembers = target.getComponentCount();
199          boolean firstVisibleComponent = true;
200          int tabIndex = 0;
201          Ruler ruler = calcTabs(target);
202 
203          for (int i = 0; i < nmembers; i++) {
204             Component m = target.getComponent(i);
205 //            if (m.isVisible()) {
206                if (isFirstInRow(m)) {
207                   tabIndex = 0;
208                   dim.width = Math.max(dim.width, rowDim.width);
209                   dim.height += rowDim.height + vgap;
210                   if (hasConstraint(m, PARAGRAPH_BREAK)) dim.height += 2*vgap;
211                   rowDim = new Dimension(0, 0);
212                }
213                if (hasConstraint(m, TAB_STOP)) rowDim.width = ruler.getTab(tabIndex++);
214                Dimension d = m.getPreferredSize();
215                rowDim.height = Math.max(rowDim.height, d.height);
216                if (firstVisibleComponent) {
217                   firstVisibleComponent = false;
218                }
219                else {
220                   rowDim.width += hgap;
221                }
222                rowDim.width += d.width;
223   //          }
224          }
225          dim.width = Math.max(dim.width, rowDim.width);
226          dim.height += rowDim.height;
227 
228          Insets insets = getInsets(target);
229          dim.width += insets.left + insets.right;// + hgap * 2;
230          dim.height += insets.top + insets.bottom;// + vgap * 2;
231          return dim;
232       }
233    }
234 
235    /***
236     * Returns the minimum dimensions needed to layout the <i>visible</i>
237     * components contained in the specified target container.
238     * @param target the component which needs to be laid out
239     * @return    the minimum dimensions to lay out the
240     *            subcomponents of the specified container
241     * @see #preferredLayoutSize
242     * @see       java.awt.Container
243     * @see       java.awt.Container#doLayout
244     */
245    public Dimension minimumLayoutSize(Container target) {
246       synchronized (target.getTreeLock()) {
247          Dimension dim = new Dimension(0, 0);
248          Dimension rowDim = new Dimension(0, 0);
249          int nmembers = target.getComponentCount();
250          boolean firstVisibleComponent = true;
251          int tabIndex = 0;
252          Ruler ruler = calcTabs(target);
253 
254          for (int i = 0; i < nmembers; i++) {
255             Component m = target.getComponent(i);
256   //          if (m.isVisible()) {
257                if (isFirstInRow(m)) {
258                   tabIndex = 0;
259                   dim.width = Math.max(dim.width, rowDim.width);
260                   dim.height += rowDim.height + vgap;
261                   if (hasConstraint(m, PARAGRAPH_BREAK)) dim.height += 2*vgap;
262                   rowDim = new Dimension(0, 0);
263                }
264                if (hasConstraint(m, TAB_STOP)) rowDim.width = ruler.getTab(tabIndex++);
265                Dimension d = m.getMinimumSize();
266                rowDim.height = Math.max(rowDim.height, d.height);
267                if (firstVisibleComponent) {
268                   firstVisibleComponent = false;
269                }
270                else {
271                   rowDim.width += hgap;
272                }
273                rowDim.width += d.width;
274 //            }
275          }
276          dim.width = Math.max(dim.width, rowDim.width);
277          dim.height += rowDim.height;
278 
279          Insets insets = getInsets(target);
280          dim.width += insets.left + insets.right;// + hgap * 2;
281          dim.height += insets.top + insets.bottom;// + vgap * 2;
282          return dim;
283       }
284    }
285 
286    /***
287     * Centers the elements in the specified row, if there is any slack.
288     * @param target the component which needs to be moved
289     * @param x the x coordinate
290     * @param y the y coordinate
291     * @param width the width dimensions
292     * @param height the height dimensions
293     * @param rowStart the beginning of the row
294     * @param rowEnd the the ending of the row
295     */
296    protected void moveComponents(Container target, int x, int y, int width,
297                                int height,
298                                int rowStart, int rowEnd, boolean ltr, Ruler ruler) {
299       synchronized (target.getTreeLock()) {
300          switch (getAlignment()) {
301             case FlowLayout.LEFT:
302                x += ltr ? 0 : width;
303                break;
304             case FlowLayout.CENTER:
305                x += width / 2;
306                break;
307             case FlowLayout.RIGHT:
308                x += ltr ? width : 0;
309                break;
310             case LEADING:
311                break;
312             case TRAILING:
313                x += width;
314                break;
315          }
316          int tabIndex = 0;
317          for (int i = rowStart; i < rowEnd; i++) {
318             Component m = target.getComponent(i);
319 //          if (m.isVisible()) {
320                if (hasConstraint(m, TAB_STOP)) x = getInsets(target).left + ruler.getTab(tabIndex++);
321                int dy = (valign == VTOP) ? 0 : (height - m.getHeight()) / 2;
322                if (ltr) {
323                   m.setLocation(x, y + dy);
324                }
325                else {
326                   m.setLocation(target.getWidth() - x - m.getWidth(),
327                                 y + dy);
328                }
329                x += m.getWidth() + hgap;
330 //            }
331          }
332       }
333    }
334 
335 
336    protected void relMove(Container target, int dx, int dy, int rowStart,
337                         int rowEnd) {
338       synchronized (target.getTreeLock()) {
339          for (int i = rowStart; i < rowEnd; i++) {
340             Component m = target.getComponent(i);
341 //            if (m.isVisible()) {
342                m.setLocation(m.getX() + dx, m.getY() + dy);
343 //            }
344          }
345 
346       }
347    }
348 
349    protected void adjustAlignment(Component m) {
350       if (hasConstraint(m, RiverLayout.LEFT)) setAlignment(FlowLayout.LEFT);
351       else if (hasConstraint(m, RiverLayout.RIGHT)) setAlignment(FlowLayout.RIGHT);
352       else if (hasConstraint(m, RiverLayout.CENTER)) setAlignment(FlowLayout.CENTER);
353       if (hasConstraint(m, RiverLayout.VTOP)) valign = VTOP;
354       else if (hasConstraint(m, RiverLayout.VCENTER)) valign = VCENTER;
355 
356    }
357    /***
358     * Lays out the container. This method lets each component take
359     * its preferred size by reshaping the components in the
360     * target container in order to satisfy the constraints of
361     * this <code>FlowLayout</code> object.
362     * @param target the specified component being laid out
363     * @see Container
364     * @see       java.awt.Container#doLayout
365     */
366    public void layoutContainer(Container target) {
367       setAlignment(FlowLayout.LEFT);
368       synchronized (target.getTreeLock()) {
369          Insets insets = getInsets(target);
370          int maxwidth = target.getWidth() -
371              (insets.left + insets.right);
372          int maxheight = target.getHeight() -
373              (insets.top + insets.bottom);
374 
375          int nmembers = target.getComponentCount();
376          int x = 0, y = insets.top + vgap;
377          int rowh = 0, start = 0, moveDownStart = 0;
378 
379          boolean ltr = target.getComponentOrientation().isLeftToRight();
380          Component toHfill = null;
381          Component toVfill = null;
382          Ruler ruler = calcTabs(target);
383          int tabIndex = 0;
384 
385          for (int i = 0; i < nmembers; i++) {
386             Component m = target.getComponent(i);
387             //if (m.isVisible()) {
388                Dimension d = m.getPreferredSize();
389                m.setSize(d.width, d.height);
390 
391                if (isFirstInRow(m)) tabIndex = 0;
392                if (hasConstraint(m, TAB_STOP)) x = ruler.getTab(tabIndex++);
393                if (!isFirstInRow(m)) {
394                   if (i > 0 && !hasConstraint(m, TAB_STOP)) {
395                      x += hgap;
396                   }
397                   x += d.width;
398                   rowh = Math.max(rowh, d.height);
399                }
400                else {
401                   if (toVfill != null && moveDownStart == 0) {
402                      moveDownStart = i;
403                   }
404                   if (toHfill != null) {
405                      toHfill.setSize(toHfill.getWidth() + maxwidth - x,
406                                      toHfill.getHeight());
407                      x = maxwidth;
408                   }
409                   moveComponents(target, insets.left, y,
410                                  maxwidth - x,
411                                  rowh, start, i, ltr, ruler);
412                   x = d.width;
413                   y += vgap + rowh;
414                   if (hasConstraint(m, PARAGRAPH_BREAK)) y += 2*vgap;
415                   rowh = d.height;
416                   start = i;
417                   toHfill = null;
418                }
419             //}
420             if (hasHfill(m)) {
421                toHfill = m;
422             }
423             if (hasVfill(m)) {
424                toVfill = m;
425             }
426             adjustAlignment(m);
427          }
428 
429          if (toVfill != null && moveDownStart == 0) {  // Don't move anything if hfill component is last component
430             moveDownStart = nmembers;
431          }
432          if (toHfill != null) { // last component
433             toHfill.setSize(toHfill.getWidth() + maxwidth - x,
434                             toHfill.getHeight());
435             x = maxwidth;
436          }
437          moveComponents(target, insets.left, y, maxwidth - x, rowh,
438                         start, nmembers, ltr, ruler);
439          int yslack = maxheight - (y+rowh);
440          if (yslack > 0 && toVfill != null) {
441             toVfill.setSize(toVfill.getWidth(), yslack + toVfill.getHeight());
442             relMove(target, 0, yslack, moveDownStart, nmembers);
443          }
444       }
445    }
446 
447 }
448 
449 class Ruler {
450    private Vector tabs = new Vector();
451 
452    public void setTab(int num, int xpos) {
453       if (num >= tabs.size()) tabs.add(num, new Integer(xpos));
454       else {
455          // Transpose all tabs from this tab stop and onwards
456          int delta = xpos - getTab(num);
457          if (delta > 0) {
458             for (int i = num; i < tabs.size(); i++) {
459                tabs.set(i, new Integer(getTab(i) + delta));
460             }
461          }
462       }
463    }
464 
465    public int getTab(int num) {
466       return ((Integer)tabs.get(num)).intValue();
467    }
468 
469    public String toString() {
470       StringBuffer ret = new StringBuffer(getClass().getName() + " {");
471       for (int i=0; i<tabs.size(); i++) {
472          ret.append(tabs.get(i));
473          if (i < tabs.size()-1) ret.append(',');
474       }
475       ret.append('}');
476       return ret.toString();
477    }
478 
479    public static void main(String[] args) {
480       Ruler r = new Ruler();
481       r.setTab(0,10);
482       r.setTab(1,20);
483       r.setTab(2,30);
484       System.out.println(r);
485       r.setTab(1,25);
486       System.out.println(r);
487       System.out.println(r.getTab(0));
488    }
489 }